#include "network/tcp_connector.hpp"

#include "base/assert.hpp"
#include "base/log.hpp"
#include "event/event.hpp"
#include "event/fd_event.hpp"
#include "event/timer_event.hpp"
#include "network/tcp_connection.hpp"

#include <sys/un.h>

namespace ibox::network {

TcpConnector::TcpConnector(event::Loop *loop)
    : m_loop(loop), m_reconn_delay_calc_func([](int) { return 1; }) {}

TcpConnector::~TcpConnector() { cleanup(); }

void TcpConnector::check_setting_and_try_enter_idle_state() {
    if (m_state == State::None) {
        if (m_server_addr.type() != SockAddr::Type::None && m_connected_cb) {
            m_state = State::Inited;
        }
    }
}

void TcpConnector::initialize(const SockAddr &server_addr) {
    m_server_addr = server_addr;
    check_setting_and_try_enter_idle_state();
}

void TcpConnector::set_connected_callback(const ConnectedCallback &cb) {
    m_connected_cb = cb;
    check_setting_and_try_enter_idle_state();
}

void TcpConnector::set_try_times(int try_times) { m_try_times = try_times; }

void TcpConnector::set_connect_fail_callback(const ConnectFailCallback &cb) {
    m_connect_fail_cb = cb;
}

void TcpConnector::set_reconnect_delay_calc_func(
    const ReconnectDelayCalc &func) {
    m_reconn_delay_calc_func = func;
}

bool TcpConnector::start() {
    if (m_state != State::Inited) {
        LOG_WARNING("not in idle state");
        return false;
    }

    m_conn_fail_times = 0;
    enter_connecting_state();
    return true;
}

void TcpConnector::stop() {
    if ((m_state == State::Inited) || (m_state == State::None)) {
        return;
    }

    if (m_state == State::Connecting) {
        exit_connecting_state();
    } else if (m_state == State::ReconnectDelay) {
        exit_reconnect_delay_state();
    }

    m_state = State::Inited;
}

void TcpConnector::cleanup() {
    if (m_state <= State::None) {
        return;
    }

    stop();

    m_connected_cb = nullptr;
    m_connect_fail_cb = nullptr;
    m_reconn_delay_calc_func = [](int) { return 1; };
    m_try_times = 0;
    m_conn_fail_times = 0;

    m_state = State::None;
}

SocketFd TcpConnector::create_socket(SockAddr::Type addr_type) const {
    int flags = SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC;

    if (addr_type == SockAddr::Type::IPv4) {
        return SocketFd::create_socket(AF_INET, flags, 0);
    }
    if (addr_type == SockAddr::Type::Local) {
        return SocketFd::create_socket(AF_LOCAL, flags, 0);
    }
    LOG_ERROR("socket type not support");
    return SocketFd();
}

int TcpConnector::connect(SocketFd sock_fd, const SockAddr &addr) const {
    int ret = -1;

    if (addr.type() == SockAddr::Type::IPv4) {
        struct sockaddr_in conn_addr;
        socklen_t addr_len = addr.to_sockaddr(conn_addr);
        ret = sock_fd.connect((struct sockaddr *) &conn_addr, addr_len);
    } else if (addr.type() == SockAddr::Type::Local) {
        struct sockaddr_un conn_addr;
        socklen_t addr_len = addr.to_sockaddr(conn_addr);
        ret = sock_fd.connect((struct sockaddr *) &conn_addr, addr_len);
    } else {
        LOG_ERROR("not support");
    }

    return ret;
}

void TcpConnector::enter_connecting_state() {
    //! 创建Socket
    SocketFd new_sock_fd = create_socket(m_server_addr.type());
    if (new_sock_fd.is_null()) {
        return;
    }

    LOG_DEBUG("server_addr: {}", m_server_addr.to_string());

    //! 连接 server_addr_ 指定的地址
    int conn_ret = connect(new_sock_fd, m_server_addr);
    int conn_errno = conn_ret == 0 ? 0 : errno;

    //! 检查错误码
    if ((conn_errno == 0) || (conn_errno == EINPROGRESS)
        || (conn_errno == EINTR) || (conn_errno == EISCONN)) {
        //! 正常情况
        m_sock_fd = std::move(new_sock_fd);

        m_write_fd = std::make_unique<event::EpollFdEvent>(
            m_loop->ptr(),
            m_sock_fd.get(),
            event::FdEvent::WriteEvent,
            event::Event::Mode::Oneshot);
        m_write_fd->set_callback(
            [&]([[maybe_unused]] uint32_t events) { on_socket_writable(); });
        m_write_fd->enable();

        m_state = State::Connecting;
        LOG_DEBUG("enter connecting state");

    } else if ((conn_errno == EAGAIN) || (conn_errno == EADDRINUSE)
               || (conn_errno == EADDRNOTAVAIL) || (conn_errno == ECONNREFUSED)
               || (conn_errno == ENETUNREACH) || (conn_errno == ENOENT)) {
        LOG_WARNING(
            "connent fail, errno: {}, {}", conn_errno, strerror(conn_errno));
        //! 条件暂时不具备
        on_connect_fail();

    } else {
        //! 如果参数不正确
        LOG_ERROR("params errno: {}, {}", conn_errno, strerror(conn_errno));
        m_state = State::None;
        //! TODO: 如果真失败了该怎么办呢？
    }
}

void TcpConnector::exit_connecting_state() {
    m_write_fd->disable();

    //! 关闭 socket
    m_sock_fd.close();
    LOG_DEBUG("exit connecting state");
}

void TcpConnector::enter_reconnect_delay_state() {
    //! 计算出要延时等待的时长
    int delay_sec = m_reconn_delay_calc_func(m_conn_fail_times);

    //! 创建定时器进行等待
    // FIXME: unique_ptr重复赋值
    m_delay_timer
        = std::make_unique<event::TimerEvent>(m_loop->ptr(),
                                              std::chrono::seconds(delay_sec),
                                              event::Event::Mode::Oneshot);
    m_delay_timer->set_callback([&] { on_delay_timeout(); });
    m_delay_timer->enable();

    m_state = State::ReconnectDelay;
    LOG_DEBUG("enter reconnect delay state, delay: {}s", delay_sec);
}

void TcpConnector::exit_reconnect_delay_state() {
    //! 关闭定时器
    m_delay_timer->disable();
    // FIXME: 删除定时器

    LOG_DEBUG("exit reconnect delay state");
}

void TcpConnector::on_connect_fail() {
    ++m_conn_fail_times;
    //! 如果设置了尝试次数，且超过了尝试次数，则回调 m_connect_fail_cb 然后回到
    //! State::Inited 否则继续进入重连等待延时状态
    if ((m_try_times > 0) && (m_conn_fail_times >= m_try_times)) {
        if (m_connect_fail_cb) {
            m_connect_fail_cb();
        } else {
            LOG_WARNING("connector stoped");
        }

        m_state = State::Inited;
    } else {
        enter_reconnect_delay_state();
    }
}

void TcpConnector::on_socket_writable() {
    //! 读取Socket错误码
    int sock_errno = 0;
    socklen_t optlen = sizeof(sock_errno);
    if (m_sock_fd.get_socket_opt(SOL_SOCKET, SO_ERROR, &sock_errno, &optlen)) {
        if (sock_errno == 0) { //! 连接成功
            SocketFd conn_sock_fd = m_sock_fd;
            m_sock_fd.reset();

            exit_connecting_state();
            m_state = State::Inited;

            LOG_INFO("connect to {} success", m_server_addr.to_string());
            if (m_connected_cb) {
                auto *conn
                    = new TcpConnection(m_loop, conn_sock_fd, m_server_addr);
                conn->enable();
                m_connected_cb(conn);
            } else {
                LOG_WARNING("connected callback is not set");
            }

        } else { //! 连接失败
            LOG_WARNING("connect fail, errno: {}, {}",
                        sock_errno,
                        strerror(sock_errno));
            //! 状态切换：Connecting --> ReconnectDelay
            exit_connecting_state();
            on_connect_fail();
        }
    } else { //! 没有读取到 SO_ERROR
        LOG_ERROR("getSocketOpt fail, errno: {}, {}", errno, strerror(errno));
    }
}

void TcpConnector::on_delay_timeout() {
    //! 状态切换：ReconnectDelay --> Connecting
    exit_reconnect_delay_state();
    enter_connecting_state();
}

} // namespace ibox::network
